﻿#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;


//实现多态还有两个必须重要条件：
//• 必须是基类的指针或者引⽤调⽤虚函数
//• 被调⽤的函数必须是虚函数，并且完成了虚函数重写 / 覆盖。

//说明：要实现多态效果，第⼀必须是基类的指针或引⽤，因为只有基类的指针或引⽤才能既指向基类
//对象⼜指向派⽣类对象；第⼆派⽣类必须对基类的虚函数完成重写 / 覆盖，重写或者覆盖了，基类和派
//⽣类之间才能有不同的函数，多态的不同形态效果才能达到。

//class Person
//{
//public:
//	virtual void BuyTicket()
//	{
//		cout << "买票全价" << endl;
//	}
//};
//
//class Student :public Person
//{
//public:
//	virtual void BuyTicket()
//	{
//		cout << "买票打折" << endl;
//	}
//};
//
//void Func(Person* ptr)
//{
//	// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket
//    // 但是跟ptr没关系，⽽是由ptr指向的对象决定的。
//	//若不构成多态，则就根据形参类型Person*来决定调用哪个BuyTicket
//	ptr->BuyTicket();
//}
//
//int main()
//{
//	Person ps;
//	Student st;
//
//	Func(&st);
//	Func(&ps);
//
//	return 0;
//}

//类成员函数前⾯加virtual修饰，那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修饰。
//虚函数的重写 / 覆盖：派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值
//	类型、函数名字、参数列表完全相同)，称派⽣类的虚函数重写了基类的虚函数。
//	注意：在重写基类虚函数时，派⽣类的虚函数在不加virtual关键字时，虽然也可以构成重写(因为继承
//		后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性)，但是该种写法不是很规范，不建议这样
//	使⽤，不过在考试选择题中，经常会故意埋这个坑，让你判断是否构成多态。

//class Animal
//{
//public:
//	virtual void talk() const
//	{
//
//	}
//};
//
//class Dog :public Animal
//{
//public:
//	virtual void talk() const
//	{
//		cout << "汪汪" << endl;
//	}
//};
//
//class Cat :public Animal
//{
//public:
//	virtual void talk() const
//	{
//		cout << "(>^ω^<)喵" << endl;
//	}
//};
//
//void Letshear(const Animal& al)
//{
//	al.talk();
//}
//
//int main()
//{
//	Cat cat;
//	Dog dog;
//
//	Letshear(cat);
//	Letshear(dog);
//
//	return 0;
//}



//首先，两个类中的func构成重写（形参看类型，不看变量名字），p->test()到A中后，test()中的this指针类型是A*，就相当于父类的指针指向了子类的对象，也就是指向了子类中被重写的虚函数，多态的两个条件形成
//所以最终调用的就是B类中的func函数，但是重写本质是上是重写虚函数的实现，所以B中func实则是这样的：virtual void func(int val = 1){ std::cout << "B->" << val << std::endl; }，所以最终是B->1

//class A
//{
//public:
//	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
//	virtual void test() { func(); }
//};
//
//
//class B : public A
//{
//public:
//	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
//};
//
//int main(int argc, char* argv[])
//{
//	B* p = new B;
//
//	p->test();//B->1
//	p->func();//B->0,此时不构成多态（给的是子类的指针，不是父类的），调用的就是B中的func(int val = 0) { std::cout << "B->" << val << std::endl; }，这时缺省值就是0，
//	//只有多态调用时，才用父类的的接口
//	return 0;
//}

//派⽣类重写基类虚函数时，与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引
//⽤，派⽣类虚函数返回派⽣类对象的指针或者引⽤时，称为协变。协变的实际意义并不⼤，所以我们
//了解⼀下即可。

//class A
//{
//
//};
//
//class B :public A
//{
//
//};
//
//class Person {
//public:
//	virtual A* BuyTicket()
//	{
//		cout << "买票-全价" << endl;
//		return nullptr;
//	}
//};
//
//class Student : public Person {
//public:
//	virtual B* BuyTicket()
//	{
//		cout << "买票-打折" << endl;
//		return nullptr;
//	}
//};
//
//void Func(Person* ptr)
//{
//	ptr->BuyTicket();
//}
//
//int main()
//{
//	Person ps;
//	Student st;
//	Func(&ps);
//	Func(&st);
//	
//	return 0;
//}


//基类的析构函数为虚函数，此时派⽣类析构函数只要定义，⽆论是否加virtual关键字，都与基类的析
//构函数构成重写，虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则，实际上编译器对析
//构函数的名称做了特殊处理，编译后析构函数的名称统⼀处理成destructor，所以基类的析构函数加了
//vialtual修饰，派⽣类的析构函数就构成重写。
//下⾯的代码我们可以看到，如果~A()，不加virtual，那么delete p2时只调⽤的A的析构函数，没有调⽤
//B的析构函数，就会导致内存泄漏问题，因为~B()中在释放资源。
//注意：这个问题⾯试中经常考察，⼤家⼀定要结合类似下⾯的样例才能讲清楚，为什么基类中的析构
//函数建议设计为虚函数。
//class A
//{
//public:
//	virtual ~A()//若这里不构成重写，那么两个析构函数就构成隐藏
//	{
//		cout << "~A()" << endl;
//	}
//};
//
//class B : public A {
//public:
//	~B()
//	{
//		cout << "~B()->delete:" << _p << endl;
//		delete _p;
//	}
//protected:
//	int* _p = new int[10];
//};
//
//// 只有派⽣类Student的析构函数重写了Person的析构函数，下⾯的delete对象调⽤析构函数，才能
////构成多态，才能保证p1和p2指向的对象正确的调⽤析构函数。
//int main()
//{
//	A* p1 = new A;
//	A* p2 = new B;
//	delete p1;
//	delete p2;
//
//	return 0;
//}

//从上⾯可以看出，C++对虚函数重写的要求⽐较严格，但是有些情况下由于疏忽，⽐如函数名写错参数
//写错等导致⽆法构成重写，⽽这种错误在编译期间是不会报出的，只有在程序运⾏时没有得到预期结
//果才来debug会得不偿失，因此C++11提供了override，可以帮助⽤⼾检测是否重写。如果我们不想让
//派⽣类重写这个虚函数，那么可以⽤final去修饰

//class Car {
//public:
//	virtual void Dirve()//函数名不同
//	{
//	}
//};
//
//class Benz :public Car {
//public:
//	virtual void Drive() override { cout << "Benz-舒适" << endl; }
//};
//int main()
//{
//	return 0;
//}

//class Car
//{
//public:
//	virtual void Drive() final {}//final修饰，无法重写
//};
//
//class Benz :public Car
//{
//public:
//	virtual void Drive() { cout << "Benz-舒适" << endl; }
//};
//
//int main()
//{
//	return 0;
//}


//在虚函数的后⾯写上 = 0 ，则这个函数为纯虚函数，纯虚函数不需要定义实现(实现没啥意义因为要被
//	派⽣类重写，但是语法上可以实现)，只要声明即可。包含纯虚函数的类叫做抽象类，抽象类不能实例
//	化出对象，如果派⽣类继承后不重写纯虚函数，那么派⽣类也是抽象类。纯虚函数某种程度上强制了
//	派⽣类重写虚函数，因为不重写实例化不出对象
//class Car
//{
//public:
//	virtual void Drive() = 0;
//};
//
//class Benz :public Car
//{
//public:
//	virtual void Drive()
//	{
//		cout << "Benz-舒适" << endl;
//	}
//};
//
//class BMW :public Car
//{
//public:
//	virtual void Drive()
//	{
//		cout << "BMW-操控" << endl;
//	}
//};
//
//int main()
//{
//	// 编译报错：error C2259: “Car”: ⽆法实例化抽象类
//	Car car;
//
//	Car* pBenz = new Benz;
//	pBenz->Drive();
//
//	Car* pBMW = new BMW;
//	pBMW->Drive();
//
//	return 0;
//}

//class Base
//{
//public:
//	virtual void Func1()
//	{
//		cout << "Func1()" << endl;
//	}
//protected:
//	int _b = 1;
//	char _ch = 'x';
//};
//
//int main()
//{
//	Base b;
//	cout << sizeof(b) << endl;
//	return 0;
//}
//上⾯题⽬运⾏结果12bytes，除了_b和_ch成员，还多⼀个__vfptr放在对象的前⾯(注意有些平台可能
//	会放到对象的最后⾯，这个跟平台有关)，对象中的这个指针我们叫做虚函数表指针(v代表virtual，f代
//	表function)。⼀个含有虚函数的类中都⾄少都有⼀个虚函数表指针，因为⼀个类所有虚函数的地址要
//	被放到这个类对象的虚函数表中，虚函数表也简称虚表。




//从底层的⻆度Func函数中ptr->BuyTicket()，是如何作为ptr指向Person对象调⽤Person::BuyTicket，的呢？
// 满⾜多态条件后，底层不再是编译时通过调⽤对象确定函数的地址，
// ⽽是运⾏时到指向的对象（若指向父类对象，则看到的就是父类对象成员，若指向的是子类对象，看到的就是子类对象对于父类对象的切片）的虚表中确定对应的虚函数的地址，
// 这样就实现了指针或引⽤指向基类就调⽤基类的虚函数，指向派⽣类就调⽤派⽣类对应的虚函数。
//class Person {
//public:
//	virtual void BuyTicket() { cout << "买票-全价" << endl; }
//private:
//	string _name;
//};
//
//class Student : public Person {
//public:
//	virtual void BuyTicket() { cout << "买票-打折" << endl; }
//private:
//	string _id;
//};
//
//class Soldier : public Person {
//public:
//	virtual void BuyTicket() { cout << "买票-优先" << endl; }
//private:
//	string _codename;
//};
//
//void Func(Person* ptr)
//{
//	//若满足多态，这⾥就是动态绑定，编译在运⾏时到ptr指向对象的虚函数表中确定调⽤函数地址
//	ptr->BuyTicket();
//	//若不满足多态，这⾥就是静态绑定，编译器直接确定调⽤Person中BuyTicket()的函数地址
//}
//
//int main()
//{
//	// 其次多态不仅仅发⽣在派⽣类对象之间，多个派⽣类继承基类，重写虚函数后
//	// 多态也会发⽣在多个派⽣类之间。
//	Person ps;
//	Student st;
//	Soldier sr;
//
//	Func(&ps);
//	Func(&st);
//	Func(&sr);
//
//	return 0;
//}

//对不满⾜多态条件(指针或者引⽤ + 调⽤虚函数)的函数调⽤是在编译时绑定，也就是编译时确定调⽤
//函数的地址，叫做静态绑定。
//• 满⾜多态条件的函数调⽤是在运⾏时绑定，也就是在运⾏时到指向对象的虚函数表中找到调⽤函数
//的地址，也就做动态绑定。






//基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共⽤同⼀张虚表，不同类型的对
//象各⾃有独⽴的虚表，所以基类和派⽣类有各⾃独⽴的虚表。
//• 派⽣类由两部分构成，继承下来的基类和⾃⼰的成员，⼀般情况下，继承下来的基类中有虚函数表
//指针，⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基
//类对象的虚函数表指针不是同⼀个，就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴
//的。
//• 派⽣类中重写的基类的虚函数，派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函
//数地址。
//• 派⽣类的虚函数表中包含，(1)基类的虚函数地址（没有被重写的），(2)派⽣类重写的虚函数地址完成覆盖，派⽣类
//⾃⼰的虚函数地址三个部分。
//• 虚函数表本质是⼀个存虚函数指针的指针数组，⼀般情况这个数组最后⾯放了⼀个0x00000000标
//记。(这个C++并没有进⾏规定，各个编译器⾃⾏定义的，vs系列编译器会再后⾯放个0x00000000
//	标记，g++系列编译不会放)
//	• 虚函数存在哪的？虚函数和普通函数⼀样的，编译好后是⼀段指令，都是存在代码段的，只是虚函
//	数的地址⼜存到了虚表中。
class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};

class Derive : public Base
{
public:
	// 重写基类的func1
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func1" << endl; }
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
//
//int main()
//{
//
//	Base b;//b中能看到的就是虚函数func1，func2
//	Derive d;//d中能看到的是被重写的func1，原来父类中未被重写的func2，以及自己的虚函数func3
//	return 0;
//}


//虚函数表存在哪的？这个问题严格说并没有标准答案C++标准并没有规定，我们写下⾯的代码可以
//对⽐验证⼀下。vs下是存在代码段(常量区)
int main()
{
	int i = 0;
	static int j = 1;
	int* p1 = new int;
	const char* p2 = "xxxxxxxx";
	printf("栈:%p\n", &i);
	printf("静态区:%p\n", &j);
	printf("堆:%p\n", p1);
	printf("常量区:%p\n", p2);


	Base b;
	Derive d;
	Base* p3 = &b;
	Derive* p4 = &d;
	printf("Person虚表地址:%p\n", *(int*)p3);
	printf("Student虚表地址:%p\n", *(int*)p4);
	printf("虚函数地址:%p\n", &Base::func1);
	printf("普通函数地址:%p\n", &Base::func5);
	return 0;
}

//运⾏结果：
//栈 : 010FF954
//静态区 : 0071D000
//堆 : 0126D740
//常量区 : 0071ABA4
//Person虚表地址 : 0071AB44
//Student虚表地址 : 0071AB84
//虚函数地址 : 00711488
//普通函数地址 : 007114BF